import {pick, remove} from 'lodash'
import GameRecorder, {IGameRecorder} from '../../match/GameRecorder'
import alg from '../../utils/algorithm'
import {eqlModelId} from "../modelId"
import {autoSerialize, autoSerializePropertyKeys, Serializable, serialize, serializeHelp} from "../serializeDecorator"
import Card, {CardType} from "./card"
import PlayerState from './playerAgent'
import Room from './room'
import Rule from './Rule'


export enum Team {
  HomeTeam = 0,
  AwayTeam = 1,
  NoTeam = 2,
}

export const genFullyCards = () => {
  const types = [CardType.Club, CardType.Diamond, CardType.Heart, CardType.Spades]
  const cards = []

  types.forEach((type: CardType) => {
    for (let v = 1; v <= 13; v += 1) {
      cards.push(new Card(type, v))
    }
  })

  cards.push(new Card(CardType.Joker, 16), new Card(CardType.Joker, 17))
  return cards
}

class Status {
  current = {seatIndex: 0, step: 1}
  lastCards: Card[] = []
  lastIndex: number = -1
  from: number
  winOrder = 0
  fen = 0
}

export function cardChangeDebugger<T extends {
  new(...args: any[]): {
    room: any
    listenPlayer(p: PlayerState): void
    listenerOn: string[]
  }

}>(constructor: T) {

  return class TableWithDebugger extends constructor {

    constructor(...args) {
      super(...args)
    }

    listenPlayer(player: PlayerState) {
      super.listenPlayer(player)
      this.listenerOn.push('game/changePlayerCards')

      player.msgDispatcher.on('game/changePlayerCards', msg => this.changePlayerCards(player, msg.cards))
    }


    changePlayerCards(player, cards) {
      let tempCards = cards.map(card => Card.from(card))
      player.cards = tempCards
      this.room.broadcast('game/changeCards', {index: player.seatIndex, cards: tempCards})
      player.sendMessage('game/changePlayerCardsReply', {ok: true, info: '换牌成功！'})
    }
  }
}

abstract class Table implements Serializable {

  restJushu: number
  turn: number

  cards: Card[]

  @autoSerialize
  remainCards: number

  @serialize
  players: PlayerState[]

  zhuang: PlayerState

  rule: Rule
  room: Room

  @autoSerialize
  state: string

  @autoSerialize
  status: Status

  onRoomEmpty: () => void
  onReconnect: (player: any, index: number) => void

  recorder: IGameRecorder

  @autoSerialize
  listenerOn: string[]

  @serialize
  stateData: any = {}

  @autoSerialize
  firstPlayerIndex: number = -1;

  @autoSerialize
  mode: 'solo' | 'teamwork' | 'unknown' = 'unknown'


  @autoSerialize
  foundFriend: boolean = false

  @autoSerialize
  friendCard: Card = null

  @autoSerialize
  tableState: string

  @autoSerialize
  autoCommitStartTime: number

  @autoSerialize
  roundWinnerIndex: number = 0

  private autoCommitTimer: NodeJS.Timer

  @autoSerialize
  maxCardsIndex: number = -1

  @autoSerialize
  zhuangFen: number = 0

  @autoSerialize
  xianFen: number = 0

  @autoSerialize
  primaryType: -1 //CardType = CardType.Spades

  @autoSerialize
  primaryValue: number = 2

  @autoSerialize
  diCards: Card[] = []

  @autoSerialize
  diCardsCounter: number = 8

  @autoSerialize
  zhuangFenCards: Card[] = []
  @autoSerialize
  xianFenCards: Card[] = []
  @autoSerialize
  fenCards: Card[] = []


  constructor(room, rule, restJushu) {
    this.restJushu = restJushu
    this.rule = rule
    this.room = room
    this.status = new Status()
    this.listenRoom(room)

    this.initCards()
    this.initPlayers()
    this.setGameRecorder(new GameRecorder(this))
  }

  toJSON() {
    return serializeHelp(this)
  }

  resume(tableStateJson) {
    const keys = autoSerializePropertyKeys(this)
    Object.assign(this, pick(tableStateJson.gameState, keys))
  }

  abstract name()

  abstract start()

  initPlayers() {
    const room = this.room
    const rule = this.rule

    const players = room.playersOrder
      .map(playerSocket => new PlayerState(playerSocket, room, rule))

    players[0].zhuang = true
    players[0].team = Team.HomeTeam
    this.zhuang = players[0]
    players.forEach(p => this.listenPlayer(p))
    this.players = players
  }

  initCards() {

    this.diCardsCounter = 3

    this.cards = genFullyCards()
    this.remainCards = this.cards.length
  }

  shuffle() {
    alg.shuffle(this.cards)
    this.turn = 1
    this.remainCards = this.cards.length
  }

  consumeCard() {
    const cardIndex = --this.remainCards
    const card = this.cards[cardIndex]
    return card
  }

  takeQuarterCards() {
    const cards = []
    const quarter = 17
    for (let i = 0; i < quarter; i++) {
      cards.push(this.consumeCard())
    }
    return cards
  }

  fapai() {
    this.shuffle()
    this.stateData = {}
    for (let i = 0, iMax = this.players.length; i < iMax; i++) {
      const p = this.players[i]
      let cards = this.takeQuarterCards()
      p.cards = cards
    }
  }

  evictPlayer(evictPlayer: PlayerState) {
    remove(this.players, p => eqlModelId(p, evictPlayer))
    this.removeRoomListenerIfEmpty()
  }

  removeRoomListenerIfEmpty() {
    if (this.empty) {
      this.removeRoomListener()
    }
  }

  removeRoomListener() {
    this.room.removeListener('reconnect', this.onReconnect)
    this.room.removeListener('empty', this.onRoomEmpty)
  }

  get empty() {
    return this.players.filter(p => p).length === 0
  }

  get playerCount() {
    return this.players.filter(p => p).length
  }

  listenPlayer(player: PlayerState) {
    this.listenerOn = ['game/cancelDeposit']

    player.msgDispatcher.on('game/cancelDeposit', msg => this.onCancelDeposit(player))
  }

  onCancelDeposit(player: PlayerState) {
    player.cancelDeposit()
  }

  broadcast(name: string, message: any) {
    this.room.broadcast(name, message)
  }

  get currentPlayerStep() {
    return this.status.current.seatIndex
  }

  isCurrentStep(player) {
    return this.currentPlayerStep === player.seatIndex
  }

  restoreMessageForPlayer(player: PlayerState) {
    const index = this.atIndex(player)

    const pushMsg = {
      index, status: [],
      mode: this.mode,
      currentPlayer: this.status.current.seatIndex,
      lastIndex: this.status.lastIndex,
      friendCard: this.friendCard,
      fen: this.status.fen,
      from: this.status.from,
      juIndex: this.room.game.juIndex,
      juShu: this.restJushu,
      foundFriend: this.foundFriend,
    }
    for (let i = 0; i < this.players.length; i++) {
      if (i === index) {
        pushMsg.status.push({
          ...this.players[i].statusForSelf(this),
          teamMateCards: this.teamMateCards(this.players[i])
        })
      } else {
        pushMsg.status.push(this.players[i].statusForOther(this))
      }
    }

    return pushMsg
  }


  showGameOverPlayerCards() {
    let playersCard = []
    this.players.forEach(p => {
      if (p.cards.length > 0) {
        playersCard.push([p.seatIndex, p.cards])
      }
    })
    this.room.broadcast('game/gameOverPlayerCards', {
      playersCard
    })
  }

  get isLastMatch() {
    return this.restJushu === 0
  }

  atIndex(player: PlayerState) {
    return this.players.findIndex(p => p._id === player._id)
  }

  getTableState() {
    const states = this.players.map(p => {
      return {
        model: p.model,
        index: p.index,
        score: p.balance,
        detail: {bomb: 0, base: 0}//p.detailBalance
      }
    })
    return states
  }

  gameOver() {
    clearTimeout(this.autoCommitTimer)
    const playersInWinOrder = this.players.slice().sort((p1, p2) => p1.winOrder - p2.winOrder)

    const teamOrder = playersInWinOrder.map(p => p.team)

    const winTeam = teamOrder[0]
    let score = 0
    if (teamOrder[0] === teamOrder[1]) {
      score = 2
      if (playersInWinOrder.slice(2).some(loser => loser.zhuaFen > 100)) {
        score = 1
      }
    } else {
      const firstTeamZhuaFen = this.players.filter(p => p.team === winTeam)
        .reduce((fen, p) => p.zhuaFen + fen, 0)

      if (firstTeamZhuaFen > 100) {
        score = 1
      } else {
        score = -1
      }

    }

    const winTeamPlayers = this.players.filter(p => p.team === winTeam)
    const loseTeamPlayers = this.players.filter(p => p.team !== winTeam)

    for (let i = 0; i < 2; i++) {
      const winner = winTeamPlayers[i]
      const loser = loseTeamPlayers[i]
      winner.winFrom(loser, score)
    }

    const states = this.players.map(p => {
      return {
        model: p.model,
        index: p.index,
        score: p.balance,
        detail: p.detailBalance
      }
    })

    const gameOverMsg = {
      states,
      juShu: this.restJushu,
      isPublic: this.room.isPublic,
      ruleType: this.rule.ruleType,
      juIndex: this.room.game.juIndex,
      creator: this.room.creator.model._id,
    }

    this.room.broadcast('game/game-over', gameOverMsg)
    this.stateData.gameOver = gameOverMsg
    this.roomGameOver(states, '')
  }

  getScoreBy(playerId) {
    return this.room.getScoreBy(playerId)
  }

  roomGameOver(states, nextStarterIndex: string) {
    this.room.gameOver(states, nextStarterIndex)
  }

  listenRoom(room) {
    room.on('reconnect', this.onReconnect = (playerMsgDispatcher, index) => {
      const player = this.players[index]
      this.replaceSocketAndListen(player, playerMsgDispatcher)

      const content = this.reconnectContent(index, player)

      player.sendMessage('game/reconnect', content)
    })

    room.once('empty',
      this.onRoomEmpty = () => {
      })
  }

  replaceSocketAndListen(player, playerMsgDispatcher) {
    player.reconnect(playerMsgDispatcher)
    this.listenPlayer(player)
  }

  reconnectContent(index, reconnectPlayer: PlayerState): any {
    const state = this.state
    const stateData = this.stateData
    const juIndex = this.room.game.juIndex

    const status = this.players.map(player => {
      return player === reconnectPlayer ? player.statusForSelf(this) : player.statusForOther(this)
    })
    return {
      index,
      state,
      juIndex,
      stateData,
      status,
    }
  }

  setGameRecorder(recorder) {
    this.recorder = recorder
    for (const p of this.players) {
      p.setGameRecorder(recorder)
    }
  }

  removeListeners(player) {
    player.removeListenersByNames(this.listenerOn)
  }

  removeAllPlayerListeners() {
    this.players.forEach(p => p.removeListenersByNames(this.listenerOn))
  }

  destroy() {
    this.removeRoomListener()
    this.removeAllPlayerListeners()
  }

  teamMateCards(player: PlayerState): Card[] {
    if (player.cards.length > 0) {
      return []
    }

    const teamMate = this.players[player.teamMate]
    if (teamMate) {
      return teamMate.cards
    }
    return []
  }


}

export default Table
